package de.richsource.gwtplug.rebind;

import java.io.PrintWriter;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import de.richsource.gwtplug.client.PluginComponentExporter;

public class PluginComponentExporterGenerator {

	/**
	 * The name of the generated class.
	 */
	protected String generatedClassName;
	/**
	 * The name of the package to use for the generated class.
	 */
	protected String packageName;

	public String generateForSuperClass(TreeLogger logger,
			GeneratorContext context, JClassType superType) {
		packageName = superType.getPackage().getName();
		generatedClassName = getGeneratedClassName(context, logger,
				superType.getSimpleSourceName());
		String qualifiedClassName = packageName + "." + generatedClassName;

		PrintWriter printWriter = context.tryCreate(logger, packageName,
				generatedClassName);

		if (printWriter == null) {
			// the generated type already exists
			return qualifiedClassName;
		}

		generateNewClass(logger, context, superType, printWriter);
		return qualifiedClassName;
	}

	/**
	 * Calculates the name of the generated class.
	 * 
	 * @param context
	 *            the GeneratorContext.
	 * @param logger
	 *            the logger to use.
	 * @param simpleSourceName
	 *            the simple source name of the supertype
	 * @return the name of the class to be generated
	 */
	private String getGeneratedClassName(GeneratorContext context,
			TreeLogger logger, String simpleSourceName) {
		return simpleSourceName + "ExporterImpl";
	}

	/**
	 * Generates the new class using the given super type (super class or
	 * interface).
	 * 
	 * @param logger
	 *            the logger to use.
	 * @param context
	 *            the GeneratorContext to use.
	 * @param superType
	 *            the super type to generate a concrete implemetation for.
	 * @param printWriter
	 *            the {@link PrintWriter} to generate the contents with.
	 */
	private void generateNewClass(TreeLogger logger, GeneratorContext context,
			JClassType superType, PrintWriter printWriter) {
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
				packageName, generatedClassName);
		composer.setSuperclass(PluginComponentExporter.class.getName());
		composer.addImport(JavaScriptObject.class.getName());

		SourceWriter sourceWriter = composer.createSourceWriter(context,
				printWriter);
		
		sourceWriter.println("protected String getType() {return \""+superType.getQualifiedSourceName()+"\";}");
		
		sourceWriter.println("protected final native void createMethods(JavaScriptObject exporter) /*-{");

		processType(superType, sourceWriter);

		sourceWriter.println("}-*/;");

		sourceWriter.commit(logger);
	}

	/**
	 * recursively processes the methods of the given type and all super classes
	 * and interfaces.
	 * 
	 * @param typeToProcess
	 *            the type to process.
	 * @param sourceWriter
	 *            the {@link SourceWriter} to use when generating the class
	 *            contents.
	 */
	private void processType(JClassType typeToProcess, SourceWriter sourceWriter) {

		// generate the implementation of all defined methods
		JMethod[] methods = typeToProcess.getMethods();
		for (JMethod method : methods) {
			// only process methods of interfaces and abstract methods.
			if (typeToProcess.isInterface() != null || method.isAbstract()) {
				generateForMethod(sourceWriter, method);
			}
		}

		// process all interfaces
		for (JClassType clazz : typeToProcess.getImplementedInterfaces()) {
			processType(clazz, sourceWriter);
		}
	}

	/**
	 * Generator method for all processed methods.
	 * 
	 * @param sourceWriter
	 *            the {@link SourceWriter} to generate the methods with.
	 * @param method
	 *            the method to process.
	 */
	protected void generateForMethod(SourceWriter sourceWriter, JMethod method) {
		JParameter[] parameters = method.getParameters();
		
		sourceWriter.print("exporter."+method.getName()+"=function(");
		for(int i=0; i<parameters.length; i++) {
			if(i>0) {
				sourceWriter.print(", ");
			}				
			sourceWriter.print(parameters[i].getName());
		}
		sourceWriter.println(") {");
		sourceWriter.println("var component = @de.richsource.gwtplug.client.PluginComponentExporter::getComponentForExporter(Lcom/google/gwt/core/client/JavaScriptObject;)(this);");
		sourceWriter.print("component."+method.getJsniSignature()+"(");
		for(int i=0; i<parameters.length; i++) {
			if(i>0) {
				sourceWriter.print(", ");
			}				
			sourceWriter.print(parameters[i].getName());
		}
		sourceWriter.println(");");
		sourceWriter.println("}");
	}


}
